package cn.sell.mbg.sys.sequence.holder;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.sell.comm.result.exceptions.SequenceException;
import cn.sell.mbg.sys.sequence.SequenceRange;
import cn.sell.mbg.sys.sequence.SysSequence;
import cn.sell.mbg.sys.sequence.mapper.SequenceMapper;

import java.util.Date;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MysqlSequenceHolder {

    private final Lock lock = new ReentrantLock();

    /**
     * seqName
     */
    private String seqName;

    /**
     * sequenceDao
     */
    private SequenceMapper sequenceMapper;

    private SysSequence sysSequence;
    /**
     *
     */
    private SequenceRange sequenceRange;
    /**
     * 是否初始化
     */
    private volatile boolean isInitialize = false;
    /**
     * sequence初始化重试次数
     */
    private int initRetryNum;
    /**
     * sequence获取重试次数
     */
    private int getRetryNum;

    /**
     * <p> 构造方法 </p>
     *
     * @param sequenceMapper dao
     * @param sysSequence    entity
     * @param initRetryNum   初始化时，数据库更新失败后重试次数
     * @param getRetryNum    获取nextVal时，数据库更新失败后重试次数
     * @return
     * @Title MysqlSequenceHolder
     * @author coderzl
     */
    public MysqlSequenceHolder(SequenceMapper sequenceMapper, SysSequence sysSequence, int initRetryNum, int getRetryNum) {
        this.sequenceMapper = sequenceMapper;
        this.sysSequence = sysSequence;
        this.initRetryNum = initRetryNum;
        this.getRetryNum = getRetryNum;
        if (sysSequence != null){
            this.seqName = sysSequence.getName();
        }
    }

    /**
     * <p> 初始化 </p>
     *
     * @param
     * @return void
     * @Title init
     * @author coderzl
     */
    public void init() {
        if (isInitialize == true) {
            throw new SequenceException("[" + seqName + "] the MysqlSequenceHolder has inited. ["+seqName+"] 不能初始化");
        }
        if (sequenceMapper == null) {
            throw new SequenceException("[" + seqName + "] the sequenceDao is null. ["+seqName+"] 数据库连接为空");
        }
        if (seqName == null || seqName.trim().length() == 0) {
            throw new SequenceException("[" + seqName + "] the sequenceName is null. ["+seqName+"] 名称为空 ");
        }
        if (sysSequence == null) {
            throw new SequenceException("[" + seqName + "] the sequenceBo is null. ["+seqName+"] 对象为空 ");
        }
        if (!sysSequence.validate()) {
            throw new SequenceException("[" + seqName + "] the sequenceBo validate fail. BO:" + sysSequence);
        }
        // 初始化该 sequence
        try {
            initSequenceRecord(sysSequence);
        } catch (SequenceException e) {
            throw e;
        }
        isInitialize = true;
    }

    /**
     * <p> 获取下一个序列号 </p>
     *
     * @param
     * @return long
     * @Title getNextVal
     * @author coderzl
     */
    public String getNextVal() {
        if (isInitialize == false) {
            throw new SequenceException("[" + seqName + "] the MysqlSequenceHolder not inited");
        }
        if (sequenceRange == null) {
            throw new SequenceException("[" + seqName + "] the sequenceRange is null");
        }
        String curValue = sequenceRange.getAndIncrement();

        if ("-1" == curValue) {
            try {
                lock.lock();
                curValue = sequenceRange.getAndIncrement();
                if ("-1" != curValue) {
                    return curValue;
                }
                sequenceRange = retryRange();
                curValue = sequenceRange.getAndIncrement();
            } finally {
                lock.unlock();
            }
        }
        return curValue;
    }

    /**
     * <p> 初始化当前这条记录 </p>
     *
     * @param
     * @return void
     * @Title initSequenceRecord
     * @Description
     * @author coderzl
     */
    private void initSequenceRecord(SysSequence sysSequence) {
        // 在限定次数内，乐观锁更新数据库记录
        for (int i = 1; i < initRetryNum; i++) {
            //查询bo
            SysSequence curBo = sequenceMapper.selectOne(sysSequence);
            if (curBo == null) {
                throw new SequenceException("[" + seqName + "] the current sequenceBo is null");
            }
            if (!curBo.validate()) {
                throw new SequenceException("[" + seqName + "] the current sequenceBo validate fail");
            }

            //改变当前值
            long newValue = curBo.getCurrent() + curBo.getStep() -1;

            //检查当前值
            if (!checkCurrentValue(newValue, curBo)) {
                throw new SequenceException("[" + seqName + "] the current reach the maximum");
//                newValue = resetCurrentValue(curBo);
            }
            sysSequence.setCurrent(newValue);
            sysSequence.setUpdateTime(DateUtil.format(new Date(),"yyyy-MM-dd HH:mm:ss"));
            int result = sequenceMapper.updateByPrimaryKeySelective(sysSequence);
            if (result > 0) {
                sequenceRange = new SequenceRange(curBo.getCurrent(), newValue,curBo);
                curBo.setCurrent(newValue);
                this.sysSequence = curBo;
                return;
            } else {
                continue;
            }
        }
        //限定次数内，更新失败，抛出异常
        throw new SequenceException("[" + seqName + "]  sequenceBo update error");
    }

    /**
     * <p> 检查新值是否合法 新的当前值是否在最大最小值之间</p>
     *
     * @param curValue
     * @param curBo
     * @return boolean
     * @author coderzl
     */
    private boolean checkCurrentValue(long curValue, SysSequence curBo) {
        if (curValue > curBo.getMin() && curValue <= curBo.getMax()) {
            return true;
        }
        return false;
    }

    /**
     * <p> 重置sequence当前值 ：当前sequence达到最大值时，重新从最小值开始 </p>
     *
     * @param curBo
     * @return long
     * @Title resetCurrentValue
     * @author coderzl
     */
    private long resetCurrentValue(SysSequence curBo) {
        return curBo.getMin();
    }

    /**
     * <p> 缓存区间使用完毕时，重新读取数据库记录，缓存新序列段 </p>
     *
     * @param
     * @Title retryRange
     * @author coderzl
     */
    private SequenceRange retryRange() {
        for (int i = 1; i < getRetryNum; i++) {
            SysSequence sysSequence = new SysSequence();
            sysSequence.setName(this.sysSequence.getName());
            //查询bo
            SysSequence curBo = sequenceMapper.selectOne(sysSequence);
            if (curBo == null) {
                throw new SequenceException("[" + seqName + "] the current sequenceBo is null");
            }
            if (!curBo.validate()) {
                throw new SequenceException("[" + seqName + "] the current sequenceBo validate fail");
            }
            //改变当前值
            long newValue = curBo.getCurrent() + curBo.getStep();
            //检查当前值
            if (!checkCurrentValue(newValue, curBo)) {
                newValue = resetCurrentValue(curBo);
            }
            Long min = curBo.getCurrent() + 1;
            curBo.setCurrent(newValue);
            int result = sequenceMapper.updateByPrimaryKeySelective(curBo);
            if (result > 0) {
                sequenceRange = new SequenceRange(min, newValue ,curBo);
                curBo.setCurrent(newValue);
                this.sysSequence = curBo;
                return sequenceRange;
            } else {
                continue;
            }
        }
        throw new SequenceException("[" + seqName + "]  sequenceBo update error");
    }
}
